﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace csis.ImmediatorClasses
{
	/// <summary>
	/// Preprocesses all the initial input files and retrieves
	/// all includes and references.
	/// This class may seem a bit redundant at first, but it simplifies
	/// the concept of having the input files and referenced assemblies
	/// possibly modified and filtered, according to preprocessing
	/// and immediator options.
	/// </summary>
	public class CompilerPreparer
	{
		public CompilerPreparer(CompilerOptions options)
		{
			if (options == null) throw new ArgumentNullException("options");
			this.options = options;
		}

		public static CompilerPreparer Run(CompilerOptions options)
		{
			var compilerPreparer = new CompilerPreparer(options);
			compilerPreparer.Run();
			return compilerPreparer;
		}

		private readonly CompilerOptions options;

		public List<Script> InputScripts { get; private set; }
		public List<string> ReferencedAssemblies { get; private set; }

		private Queue<string> newScriptPaths;

		public void Run()
		{
			this.newScriptPaths = new Queue<string>(options.InputFiles);
			this.InputScripts = new List<Script>();
			this.ReferencedAssemblies = new List<string>(options.ReferencedAssemblies.Distinct());

			if (this.options.ScriptHelpers)
			{
				this.IncludeScriptHelpers();
			}

			while (newScriptPaths.Any())
			{
				var filePath = newScriptPaths.Dequeue();
				var script = new Script(filePath);

				if (!this.InputScripts.Any(x => x.FilePath == filePath))
				{
					this.InputScripts.Add(script);
				}

				if (this.options.NoPreprocessor)
				{
					continue;
				}

				var preprocessor = Preprocessor.Run(script);
				foreach (var file in preprocessor.IncludedFiles)
				{
					newScriptPaths.Enqueue(file);
				}
				foreach (var assembly in preprocessor.ReferencedAssemblies)
				{
					if (!this.ReferencedAssemblies.Contains(assembly))
					{
						this.ReferencedAssemblies.Add(assembly);
					}
				}

				if (preprocessor.ScriptHelpersRequested)
				{
					this.IncludeScriptHelpers();
				}
			}
		}

		private bool scriptHelpersAdded;
		private TemporaryFile scriptHelpers;

		private void IncludeScriptHelpers()
		{
			if (this.scriptHelpersAdded)
			{
				return;
			}
			this.scriptHelpersAdded = true;

			this.scriptHelpers = new TemporaryFile();
			File.WriteAllText(scriptHelpers.FileName, Properties.Resources.ScriptHelpers);
			this.newScriptPaths.Enqueue(this.scriptHelpers.FileName);
		}
	}
}
